package com.hhf.feiojcodesandbox.service.impl.old;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.hhf.feiojcodesandbox.model.ExecuteCodeRequest;
import com.hhf.feiojcodesandbox.model.ExecuteCodeResponse;
import com.hhf.feiojcodesandbox.model.ExecuteMessage;
import com.hhf.feiojcodesandbox.model.JudgeInfo;
import com.hhf.feiojcodesandbox.service.CodeSandbox;
import com.hhf.feiojcodesandbox.service.impl.JavaDockerCodeSandbox;
import com.hhf.feiojcodesandbox.utils.ProcessUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

/**
 * @Author hhf
 * @Date 2024/10/31
 * @Description Java Docker代码沙箱
 */
@Slf4j
@Deprecated
public class JavaDockerCodeSandboxOld implements CodeSandbox {

    private static final String GLOBAL_CODE_DIR_NAME = "tempCode";

    private static final String GLOBAL_JAVA_CLASS_NAME = "Main.java";

    private static final boolean ONCE_INIT = true;

    // TODO 调试阶段,自己创建
//    @Resource
//    private DockerClient dockerClient;


    public static void main(String[] args) {
        JavaDockerCodeSandbox javaDockerCodeSandbox = new JavaDockerCodeSandbox();

        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
        executeCodeRequest.setInputList(Arrays.asList("1 2", "1 3"));
        String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Add.java", StandardCharsets.UTF_8);

//        String code = ResourceUtil.readStr("testCode/unSafe/ReadFile.java", StandardCharsets.UTF_8);

        executeCodeRequest.setCode(code);
        executeCodeRequest.setLanguage("java");


        ExecuteCodeResponse response = javaDockerCodeSandbox.executeCode(executeCodeRequest);
        if (response == null) {
            log.error("发生错误");
        } else {
            log.info(response.toString());
        }
    }

    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {


        List<String> inputList = executeCodeRequest.getInputList();
        String code = executeCodeRequest.getCode();


        log.info("代码沙箱开始执行...");
        //1. 把用户的代码保存为文件 (不变)
        String userDir = System.getProperty("user.dir");
        String golbalCodePathName = userDir + File.separator + GLOBAL_CODE_DIR_NAME;
        if (!FileUtil.exist(golbalCodePathName)) {
            FileUtil.mkdir(golbalCodePathName);
        }
        //将不同用户的提交代码进行隔离 (不变)
        String random = UUID.randomUUID().toString();
        String userCodeParentPath = golbalCodePathName + File.separator + random;
        String userCodePath = userCodeParentPath + File.separator + GLOBAL_JAVA_CLASS_NAME;
        File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);

        //2. 编译文件 => .class (不变)
        String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsoluteFile());
        try {
            Process compileProcess = Runtime.getRuntime().exec(compileCmd);
            ExecuteMessage compileExecuteMessage = ProcessUtils.runProcessAndGetMessage(compileProcess, "编译");
            log.info(compileExecuteMessage.toString());
        } catch (IOException e) {
            e.printStackTrace();
            return getErrorResponse(e);
        }

        //3. 执行.class文件,获取执行结果

        //先创建能运行java的docker容器
        String image = "openjdk:8-alpine";
        String containerId = null;

        // TODO 调试阶段,自己创建
        DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
                .withDockerHost("tcp://192.168.140.128:2375")
                .build();
        //创建Docker客户端
        DockerClient dockerClient =  DockerClientBuilder.getInstance(config).build();


        if (ONCE_INIT) {

//            // 拉取镜像
//            PullImageResultCallback pullImageResultCallback = new PullImageResultCallback() {
//                @Override
//                public void onNext(PullResponseItem item) {
//                    log.info("正在拉取远程镜像:{}", item.getStatus());
//                    super.onNext(item);
//                }
//            };
//            try {
//                dockerClient.pullImageCmd(image).exec(pullImageResultCallback).awaitCompletion();
//            } catch (InterruptedException e) {
//                log.error("拉取镜像异常");
//                throw new RuntimeException(e);
//            }
            //创建容器
            CreateContainerCmd containerCmd = dockerClient.createContainerCmd(image);

            HostConfig hostConfig = new HostConfig();
            hostConfig.withMemory(100 * 1000 * 1000L);  //设置最大存储
            hostConfig.withMemorySwap(0L);
            hostConfig.withCpuCount(1L);                //设置cpu运行核数
//            hostConfig.setBinds(new Bind(userCodeParentPath, new Volume("/app")));  //将用户代码映射到/app目录
            hostConfig.setBinds(new Bind("/home/hhf/project/feioj-code-sandbox/tempCode/", new Volume("/app")));  //将用户代码映射到/app目录


            CreateContainerResponse response = containerCmd
                    .withHostConfig(hostConfig)
                    .withNetworkDisabled(true)
                    .withReadonlyRootfs(true)
                    .withAttachStdin(true)
                    .withAttachStderr(true)
                    .withAttachStdout(true)
                    .withTty(true)
                    .exec();

            containerId = response.getId();
            log.info("容器创建ID:{}", containerId);

            //启动容器
            dockerClient.startContainerCmd(containerId).exec();
            log.info("容器{}正在启动...", containerId);

        }


        List<ExecuteMessage> executeMessageList = new ArrayList<>();

        for (String inputArgs : inputList) {

            //java -cp /app/wetqwq43214 Main 1 2
            String[] inputArgsArray = inputArgs.split(" ");
//            String[] cmdArray = ArrayUtil.append(new String[]{"java", "-cp", "/app", "Main"}, inputArgsArray);
            String[] cmdArray = ArrayUtil.append(new String[]{"java", "-cp", "/app/" + random, "Main"}, inputArgsArray);


            ExecCreateCmdResponse execResponse = dockerClient.execCreateCmd(containerId)
                    .withCmd(cmdArray)
                    .withAttachStderr(true)
                    .withAttachStdin(true)
                    .withAttachStdout(true)
                    .exec();


            log.info("创建执行命令:{}", String.join(" ",cmdArray));
            String execId = execResponse.getId();

            //执行命令,并且统计执行时间和占用最大内存

            //统计执行时间
            StopWatch stopWatch = new StopWatch();
            long time = 0L;
            final boolean[] timeout = {true};
            final String[] message = {null};
            final String[] errorMessage = {null};

            ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback() {
                @Override
                public void onNext(Frame frame) {

                    StreamType type = frame.getStreamType();
                    if (StreamType.STDERR.equals(type)){
                        errorMessage[0] = new String(frame.getPayload());
                        log.error("exec code error:{}",errorMessage[0]);
                    } else {
                        message[0] = new String(frame.getPayload());
                        log.info("exec code success:{}",message[0]);
                    }


                    super.onNext(frame);
                }

                @Override
                public void onComplete() {
                    //如果执行完成,则表示没有超时
                    timeout[0] = false;
                    super.onComplete();
                }
            };

            try {
                //开始计时
                stopWatch.start();
                dockerClient.execStartCmd(execId)
                        .exec(execStartResultCallback)
                        .awaitCompletion();
                stopWatch.stop();
                time = stopWatch.getLastTaskTimeMillis();
                if (timeout[0]){
                    log.error("程序执行发生错误...");
                }else {
                    log.info("程序执行时间:{}ms",time);
                }

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }


            //占用最大内存
            final long[] memory = {0L};
            StatsCmd statsCmd = dockerClient.statsCmd(containerId);
            statsCmd.exec(new ResultCallback<Statistics>() {
                @Override
                public void onStart(Closeable closeable) {

                }

                @Override
                public void onNext(Statistics statistics) {
                    Long usage = statistics.getMemoryStats().getUsage();
                    memory[0] = usage;
                    log.info("内存最大占用:{}", usage);
                }

                @Override
                public void onError(Throwable throwable) {

                }

                @Override
                public void onComplete() {

                }

                @Override
                public void close() throws IOException {

                }
            });
            ExecuteMessage executeMessage = new ExecuteMessage();
            executeMessage.setMessage(message[0]);
            executeMessage.setErrorMessage(errorMessage[0]);
            executeMessage.setTime(time);
            executeMessage.setMemory(memory[0]);
            executeMessageList.add(executeMessage);


        }
        //4. 封装结果,构造响应对象
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        List<String> outputList = new ArrayList<>();
        // 输出用例 , 执行时间(取用时最大值，便于判断是否超时)
        long maxTime = 0;
        for (ExecuteMessage executeMessage : executeMessageList) {
            String errorMessage = executeMessage.getErrorMessage();
            if (StrUtil.isNotBlank(errorMessage)) {
                executeCodeResponse.setMessage(errorMessage);
                // 用户提交的代码执行中存在错误
                executeCodeResponse.setMessage("3");
                break;
            }
            outputList.add(executeMessage.getMessage());
            Long time = executeMessage.getTime();
            if (time != null) {
                maxTime = Math.max(maxTime, time);
            }
        }
        // 正常运行完成
        if (outputList.size() == executeMessageList.size()) {
            // 用户提交的代码正常运行完成
            executeCodeResponse.setMessage("1");
        }
        executeCodeResponse.setOutputList(outputList);
        JudgeInfo judgeInfo = new JudgeInfo();
        judgeInfo.setTime(maxTime);
        //执行内存
        // 要借助第三方库来获取内存占用，非常麻烦，此处不做实现

        executeCodeResponse.setJudgeInfo(judgeInfo);


        //5. 文件清理 (不变)
//        if (userCodeFile.getParent() != null){
//            boolean del = FileUtil.del(userCodeParentPath);
//            System.out.println("删除" + (del ? "成功" : "失败"));
//        }
        //日志记录
        log.info("代码沙箱执行完毕...");


        //返回结果
        return executeCodeResponse;
    }

    /**
     * 获取错误处理
     *
     * @param e
     * @return
     */
    private ExecuteCodeResponse getErrorResponse(Throwable e) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setOutputList(new ArrayList<>());
        executeCodeResponse.setMessage(e.getMessage());

        // 表示代码沙箱错误
        executeCodeResponse.setMessage("2");
        executeCodeResponse.setJudgeInfo(new JudgeInfo());

        return executeCodeResponse;
    }
}
